package com.lbt.mybatis.plus.extend.util;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.map.WeakConcurrentMap;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.lbt.mybatis.plus.extend.annotation.MPESql;
import com.lbt.mybatis.plus.extend.annotation.Where;
import com.lbt.mybatis.plus.extend.bean.*;
import com.lbt.mybatis.plus.extend.context.MybatisPlusExtendBeanContext;
import com.lbt.mybatis.plus.extend.expression.SqlExpression;
import com.lbt.mybatis.plus.extend.field.FieldValueConverter;
import com.lbt.mybatis.plus.extend.field.FieldValueFormatter;
import com.lbt.mybatis.plus.extend.json.MybatisPlusExtendJsonDeserializer;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SetOperationList;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zhangjinyu
 * @since 2023-05-16
 */
@Slf4j
public class MybatisPlusExtendSqlUtils {
    private static final WeakConcurrentMap<Class<? extends FieldValueFormatter<?>>, FieldValueFormatter<?>> FIELD_VALUE_FORMATTER_CACHE = new WeakConcurrentMap<>();
    private static final WeakConcurrentMap<Class<? extends FieldValueConverter>, FieldValueConverter> FIELD_VALUE_CONVERTER_CACHE = new WeakConcurrentMap<>();
    public static String AND = "and";
    public static String OR = "or";

    public static Optional<QueryField> findQueryField(Object parameter) {
        if (parameter != null) {
            if (parameter instanceof Map<?, ?> parameterMap) {
                for (Map.Entry<?, ?> entry : parameterMap.entrySet()) {
                    if (entry.getValue() != null && entry.getValue() instanceof QueryField) {
                        return Optional.of((QueryField) entry.getValue());
                    }
                }
            } else if (parameter instanceof QueryField) {
                return Optional.of((QueryField) parameter);
            }
        }
        return Optional.empty();

    }

    public static List<Field> filterFieldBySqlFieldsAnnotation(QueryField queryField) {
        if (Objects.isNull(queryField)) {
            return Collections.emptyList();
        }
        Field[] fields = ReflectUtil.getFields(queryField.getClass());
        if (Objects.isNull(fields)) {
            return Collections.emptyList();
        }
        return Arrays.stream(fields).filter(field -> AnnotationUtil.hasAnnotation(field, MPESql.class)).toList();
    }

    public static List<QueryFieldObjectItemParent> createQueryObjectItems(QueryField queryField) {
        if (Objects.isNull(queryField)) {
            return Collections.emptyList();
        }
        List<Field> fields = filterFieldBySqlFieldsAnnotation(queryField);
        if (fields.isEmpty()) {
            return Collections.emptyList();
        }
        Map<String, Field> fieldMap = fields.stream().filter(field -> {
            MPESql mpeSql = AnnotationUtil.getAnnotation(field, MPESql.class);
            Where[] where = mpeSql.where();
            return !ObjectUtils.isEmpty(where);
        }).collect(Collectors.toMap(Field::getName, value -> value));
        if (ObjectUtils.isEmpty(fieldMap)) {
            return Collections.emptyList();
        }
        List<QueryFieldValueItemParent> queryFieldValueItemParents = queryField2Object(fieldMap, queryField.getQueryField());
        if (queryFieldValueItemParents.isEmpty()) {
            return Collections.emptyList();
        }
        return buildQueryFieldObjectItemParent(fieldMap, queryFieldValueItemParents);
    }

    private static List<QueryFieldObjectItemParent> buildQueryFieldObjectItemParent(Map<String, Field> fieldMap, List<QueryFieldValueItemParent> queryFieldValueItemParents) {
        List<QueryFieldObjectItemParent> queryFieldObjectItemParents = new ArrayList<>(queryFieldValueItemParents.size());
        for (QueryFieldValueItemParent queryFieldValueItemParent : queryFieldValueItemParents) {
            QueryFieldObjectItemParent queryFieldObjectItemParent = new QueryFieldObjectItemParent();
            queryFieldObjectItemParent.setOperator(queryFieldValueItemParent.getOperator());
            List<QueryFieldValueItem> items = queryFieldValueItemParent.getItems();
            List<QueryFieldObjectItem> queryFieldObjectItems = new ArrayList<>(items.size());
            for (QueryFieldValueItem item : items) {
                QueryFieldObjectItem queryFieldObjectItem = new QueryFieldObjectItem();
                queryFieldObjectItem.setOperator(item.getOperator());
                List<QueryFieldValue> queryFieldValues = item.getItems();
                List<QueryFieldObject> queryFieldObjects = new ArrayList<>(queryFieldValues.size());
                for (QueryFieldValue queryFieldValue : queryFieldValues) {
                    Boolean nullValue = queryFieldValue.getNullValue();
                    Field field = fieldMap.get(queryFieldValue.getFieldName());
                    for (Where where : AnnotationUtil.getAnnotation(field, MPESql.class).where()) {
                        FieldValueFormatter<?> fieldValueFormatter = FIELD_VALUE_FORMATTER_CACHE.computeIfAbsent(where.fieldValueFormatter(), MybatisPlusExtendSqlUtils::newInstanceClass);
                        FieldValueConverter fieldValueConverter = FIELD_VALUE_CONVERTER_CACHE.computeIfAbsent(where.filedValueConverter(), MybatisPlusExtendSqlUtils::newInstanceClass);
                        if (fieldValueFormatter.support(queryFieldValue.getFieldValue())) {
                            QueryFieldObject queryFieldObject = new QueryFieldObject();
                            queryFieldObject.setSqlTable(where.sqlTable());
                            queryFieldObject.setSqlColumn(where.sqlColumn());
                            queryFieldObject.setFieldType(field.getType());
                            queryFieldObject.setSqlColumnValue(fieldValueConverter.getSqlFieldValue(fieldValueFormatter.format(queryFieldValue.getFieldValue())));
                            queryFieldObject.setNullValue(nullValue);
                            queryFieldObject.setSqlExpression(where.expression());
                            queryFieldObject.setUnionIndex(where.unionIndex());
                            queryFieldObject.setResultWrap(where.resultWrap());
                            if (queryFieldObject.unionWrapConflict()) {
                                throw new RuntimeException("MPE=>类:{}中的字段:{}的设置unionIndex与resultWrap冲突");
                            }
                            queryFieldObjects.add(queryFieldObject);
                        }
                    }
                }
                queryFieldObjectItem.setQueryFieldObjects(queryFieldObjects);
                queryFieldObjectItems.add(queryFieldObjectItem);
            }
            queryFieldObjectItemParent.setQueryFieldObjectItems(queryFieldObjectItems);
            queryFieldObjectItemParents.add(queryFieldObjectItemParent);
        }
        return queryFieldObjectItemParents;
    }

    private static List<QueryFieldValueItemParent> queryField2Object(Map<String, Field> fieldMap, String queryFieldJson) {
        if (!StringUtils.hasText(queryFieldJson)) {
            return Collections.emptyList();
        }
        MybatisPlusExtendJsonDeserializer mybatisPlusExtendJsonDeserializer = MybatisPlusExtendBeanContext.getBean(MybatisPlusExtendJsonDeserializer.class);
        List<QueryFieldValueItemParent> queryFieldValueObjectParents;
        try {
            queryFieldValueObjectParents = mybatisPlusExtendJsonDeserializer.queryFieldDeserialize(queryFieldJson);
            queryFieldValueObjectParents = queryFieldValueObjectParents.stream().filter(queryFieldValueItemParent -> StrUtil.equalsAnyIgnoreCase(queryFieldValueItemParent.getOperator(), AND, OR) && queryFieldValueItemParent.getItems().stream().anyMatch(queryFieldValueItem -> StrUtil.equalsAnyIgnoreCase(queryFieldValueItem.getOperator(), AND, OR) && queryFieldValueItem.getItems().stream().anyMatch(v -> StringUtils.hasText(v.getFieldName()) && (Objects.nonNull(v.getFieldValue()) || Objects.nonNull(v.getNullValue())) && fieldMap.containsKey(v.getFieldName())))).toList();
        } catch (Exception ignore) {
            queryFieldValueObjectParents = Collections.emptyList();
        }
        return queryFieldValueObjectParents;
    }

    public static void buildMpeBoundSql(MPEBoundSql mpeBoundSql, String originalSql, List<QueryFieldObjectItemParent> queryFieldObjectItemParents) throws JSQLParserException {
        if (ObjectUtils.isEmpty(queryFieldObjectItemParents)) {
            return;
        }
        Select select = (Select) CCJSqlParserUtil.parse(originalSql);
        SelectBody selectBody = select.getSelectBody();
        if (selectBody instanceof PlainSelect plainSelect) {
            plainSelectRewrite(mpeBoundSql, -1, plainSelect, queryFieldObjectItemParents);
        } else if (selectBody instanceof SetOperationList setOperationList) {
            setOperationListRewrite(mpeBoundSql, setOperationList, queryFieldObjectItemParents);
        }
        resultWrap(select.toString(), mpeBoundSql, queryFieldObjectItemParents);
    }

    private static void setOperationListRewrite(MPEBoundSql mpeBoundSql, SetOperationList setOperationList, List<QueryFieldObjectItemParent> queryFieldObjectItemParents) {
        List<SelectBody> selects = setOperationList.getSelects();
        for (int i = 0; i < selects.size(); i++) {
            SelectBody select = selects.get(i);
            if (select instanceof PlainSelect plainSelect) {
                plainSelectRewrite(mpeBoundSql, i, plainSelect, queryFieldObjectItemParents);
            }
            // else  Do nothing
        }
    }

    private static void plainSelectRewrite(MPEBoundSql mpeBoundSql, int index, PlainSelect plainSelect, List<QueryFieldObjectItemParent> queryFieldObjectItemParents) {
        Expression originExpression = plainSelect.getWhere();
        plainSelect.setWhere(createParentWhereSql(mpeBoundSql, index, false, originExpression, queryFieldObjectItemParents));
    }

    private static Expression createParentWhereSql(MPEBoundSql mpeBoundSql, int index, boolean resultWrap, Expression expression, List<QueryFieldObjectItemParent> queryFieldObjectItemParents) {
        StringJoiner stringJoiner = new StringJoiner(" ");
        Expression expressionResult = expression;
        for (int i = 0; i < queryFieldObjectItemParents.size(); i++) {
            QueryFieldObjectItemParent queryFieldObjectItemParent = queryFieldObjectItemParents.get(i);
            String itemWhereSql = createItemWhereSql(mpeBoundSql, index, resultWrap, queryFieldObjectItemParent.getOperator(), queryFieldObjectItemParent.getQueryFieldObjectItems());
            if (Objects.nonNull(itemWhereSql)) {
                stringJoiner.add(itemWhereSql);
                expressionResult = getExpression(expression, stringJoiner.toString(), queryFieldObjectItemParent);
            }
        }
        return expressionResult;
    }

    private static Expression getExpression(Expression expression, String whereSql, QueryFieldObjectItemParent queryFieldObjectItemParent) {
        Expression sqlExpression;
        try {
            sqlExpression = CCJSqlParserUtil.parseCondExpression(whereSql);
        } catch (JSQLParserException e) {
            log.error("SQL Where 条件错误:{}", whereSql);
            throw new RuntimeException("高级查询参数错误:" + whereSql);
        }
        if (Objects.isNull(expression)) {
            return sqlExpression;
        } else {
            BinaryExpression binaryExpression = getExpression(expression, queryFieldObjectItemParent.getOperator());
            if (binaryExpression != null) {
                binaryExpression.setRightExpression(sqlExpression);
            }
            return binaryExpression;
        }
    }

    private static String createItemWhereSql(MPEBoundSql mpeBoundSql, int index, boolean resultWrap, String operator, List<QueryFieldObjectItem> queryFieldObjectItems) {
        StringJoiner stringJoiner = new StringJoiner(" ");
        for (int i = 0; i < queryFieldObjectItems.size(); i++) {
            QueryFieldObjectItem queryFieldObjectItem = queryFieldObjectItems.get(i);
            List<QueryFieldObject> queryFieldObjects = queryFieldObjectItem.getQueryFieldObjects().stream().filter(queryFieldObject -> queryFieldObject.isResultWrap() == resultWrap && (queryFieldObject.getUnionIndex() == -1 || queryFieldObject.getUnionIndex() == index)).toList();
            String whereSql = createWhereSql(mpeBoundSql, queryFieldObjectItem.getOperator(), queryFieldObjects);
            if (Objects.nonNull(whereSql)) {
                if (i > 0 && Objects.nonNull(operator)) {
                    stringJoiner.add(operator);
                }
                stringJoiner.add(whereSql);
            }
        }
        String itemWhereSql = stringJoiner.toString();
        return StrUtil.isBlank(itemWhereSql) ? null : itemWhereSql;
    }

    private static String createWhereSql(MPEBoundSql mpeBoundSql, String operator, List<QueryFieldObject> queryFieldObjects) {
        if (queryFieldObjects.isEmpty()) {
            return null;
        }
        StringJoiner sqlStringJoiner = queryFieldObjects.size() == 1 ? new StringJoiner(" " + operator + " ") : new StringJoiner(" " + operator + " ", "(", ")");
        for (QueryFieldObject queryFieldObject : queryFieldObjects) {
            sqlStringJoiner.add(StrFormatter.formatWith(queryFieldObject.getSqlExpression().getExpression(), SqlExpression.PLACE_HOLDER, queryFieldObject.getSqlColumn()));
            Object sqlColumnValue = queryFieldObject.getSqlColumnValue();
            if (sqlColumnValue instanceof Collection<?> value) {
                int paramIndex = 0;
                for (Object next : value) {
                    mpeBoundSql.addProperties(queryFieldObject.getSqlColumn() + paramIndex, next);
                    paramIndex += 1;
                }
            } else if (sqlColumnValue instanceof Object[] value) {
                int paramIndex = 0;
                for (Object next : value) {
                    mpeBoundSql.addProperties(queryFieldObject.getSqlColumn() + paramIndex, next);
                    paramIndex += 1;
                }
            } else {
                mpeBoundSql.addProperties(queryFieldObject.getSqlColumn(), sqlColumnValue);
            }
        }
        return sqlStringJoiner.toString();
    }


    private static <T> T newInstanceClass(Class<T> clazz) {
        try {
            return clazz.getConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                 NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    private static BinaryExpression getExpression(Expression originExpression, String operator) {
        if (StrUtil.equalsIgnoreCase(operator, AND)) {
            AndExpression andExpression = new AndExpression();
            if (Objects.nonNull(originExpression)) {
                andExpression.setLeftExpression(originExpression);
            }
            return andExpression;
        }
        if (StrUtil.equalsIgnoreCase(operator, OR)) {
            OrExpression orExpression = new OrExpression();
            if (Objects.nonNull(originExpression)) {
                orExpression.setLeftExpression(originExpression);
            }
            return orExpression;
        }
        return null;
    }

    private static void resultWrap(String sql, MPEBoundSql mpeBoundSql, List<QueryFieldObjectItemParent> queryFieldObjectItemParents) {
        List<QueryFieldObjectItemParent> list = queryFieldObjectItemParents.stream().filter(queryFieldObjectItemParent -> Objects.nonNull(queryFieldObjectItemParent.getQueryFieldObjectItems()) && !queryFieldObjectItemParent.getQueryFieldObjectItems().stream().filter(queryFieldObjectItem -> Objects.nonNull(queryFieldObjectItem.getQueryFieldObjects()) && !queryFieldObjectItem.getQueryFieldObjects().stream().filter(QueryFieldObject::isResultWrap).toList().isEmpty()).toList().isEmpty()).toList();
        if (list.isEmpty()) {
            mpeBoundSql.setSql(sql);
            return;
        }
        String wrapAlias = "mpeResult";
        String sqlWrap = "select * from (" + sql + ") " + wrapAlias;
        Select select;
        try {
            select = (Select) CCJSqlParserUtil.parse(sqlWrap);
        } catch (JSQLParserException e) {
            log.error("SQL Where 条件错误:{}", sqlWrap);
            throw new RuntimeException("扩展查询参数错误:" + sqlWrap);
        }
        PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
        Expression expression = plainSelect.getWhere();
        plainSelect.setWhere(createParentWhereSql(mpeBoundSql, -1, true, expression, list));
        mpeBoundSql.setSql(select.toString());
    }


}
